﻿namespace DCHealthCheck.DomainModel {

    #region Usings
    using DCHealthCheck.Common;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks; 
    #endregion

    [Table("NetstatInfo")]
    public class NetstatInfo : INetstatInfo, IObjectWithState, IValidatableObject {

        #region Members
        #region Max length fields
        public static readonly int LocalAddressMaxLength = 255;
        public static readonly int ForeignAddressMaxLength = 255;
        #endregion

        public static readonly string CSVHeader =
            "DCName,Domain,ADSite,OSVersion,HealthCheckDate," +
            "ProcessId,ExecutablePath,Protocol,LocalAddress,LocalPort,ForeignAddress,ForeignPort,State";

        public virtual string ForeignAddress { get; set; }

        public virtual int ForeignPort { get; set; }

        public virtual long Id { get; set; }

        public virtual string LocalAddress { get; set; }

        public virtual int LocalPort { get; set; }

        public virtual ConnectionProtocol Protocol {
            get { return protocol; }
            set { protocol = value; }
        }
        private ConnectionProtocol protocol;

        public virtual NetworkConnectionState State {
            get { return state; }
            set { state = value; }
        }
        private NetworkConnectionState state;

        public virtual long ProcessId { get; set; }

        #region Related Entities
        [ForeignKey("ProcessInfo")]
        public virtual Nullable<long> ProcessInfoRecordId {
            get {
                if (this.ProcessInfo != null) return this.ProcessInfo.Id;
                return processInfoRecordId;
            }
            set { processInfoRecordId = value; }
        }
        private Nullable<long> processInfoRecordId;

        public virtual ProcessInfo ProcessInfo {
            get { return processInfo; }
            set {
                processInfo = value;
                if (processInfo != null) {
                    if (this.ProcessInfoRecordId != processInfo.Id) {
                        this.ProcessInfoRecordId = processInfo.Id;
                    }
                }
            }
        }
        private ProcessInfo processInfo;

        [ForeignKey("SystemInfo")]
        public virtual Guid SystemInfoId {
            get {
                if (this.SystemInfo != null) return this.SystemInfo.Id;
                return systemInfoId;
            }
            set { systemInfoId = value; }
        }
        private Guid systemInfoId;

        public virtual SystemInfo SystemInfo {
            get { return systemInfo; }
            set {
                systemInfo = value;
                if (systemInfo != null) {
                    if (this.SystemInfoId != systemInfo.Id) {
                        this.SystemInfoId = systemInfo.Id;
                    }
                }
            }
        }
        private SystemInfo systemInfo;
        #endregion

        #region NotMapped properties
        [NotMapped]
        public DetachedState DetachedState { get; set; }

        [NotMapped]
        public Object EntityKey { get { return this.Id; } }

        [NotMapped]
        public Dictionary<string, object> StartingOriginalValues { get; set; }
        #endregion 
        #endregion

        #region Constructors
        public NetstatInfo() {
            this.Protocol = ConnectionProtocol.Undefined;
            this.LocalAddress = string.Empty;
            this.ForeignAddress = string.Empty;
            this.State = NetworkConnectionState.Undefined;
        }

        public NetstatInfo(string line)
            : this() {
            string[] lineParsed = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if ((lineParsed.Count() == 4) || (lineParsed.Count() == 5)) {
                Enum.TryParse<ConnectionProtocol>(lineParsed[0], true, out protocol);
                this.LocalAddress = lineParsed[1].Substring(0, lineParsed[1].LastIndexOf(":"));
                this.LocalPort = Convert.ToInt32(lineParsed[1].Substring(lineParsed[1].LastIndexOf(":") + 1));
                this.ForeignAddress = lineParsed[2].Substring(0, lineParsed[2].LastIndexOf(":"));
                this.ForeignPort = Convert.ToInt32(lineParsed[1].Substring(lineParsed[1].LastIndexOf(":") + 1));

                if (lineParsed.Count() == 5) {
                    Enum.TryParse<NetworkConnectionState>(lineParsed[3], true, out state);
                    this.ProcessId = Convert.ToInt32(lineParsed[4]);
                }
                else {
                    this.ProcessId = Convert.ToInt32(lineParsed[3]);
                }
            }
        } 
        #endregion

        #region Methods
        public string ToCSVString() {
            var info = new StringBuilder();

            #region DC Info
            if ((this.SystemInfo != null) && (this.SystemInfo.DomainController != null)) {
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.SamAccountNameFriendly);
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.NetbiosDomain);
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.ADSite);
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.OSVersion);
            }
            else {
                info.Append(",,,,");
            }
            #endregion

            if (this.SystemInfo != null) {
                info.AppendFormat("\"{0}\",", this.SystemInfo.CheckDateTimeUTC.ToString("yyyy-MM-dd HH:mm"));
            }
            else {
                info.Append(",");
            }

            info.AppendFormat("\"{0}\",", this.ProcessId);

            if (this.ProcessInfo != null) {
                info.AppendFormat("\"{0}\",", this.ProcessInfo.ExecutablePath);
            }
            else {
                info.Append(",");
            }

            info.AppendFormat("\"{0}\",", this.Protocol);
            info.AppendFormat("\"{0}\",", this.LocalAddress);
            info.AppendFormat("\"{0}\",", this.LocalPort);
            info.AppendFormat("\"{0}\",", this.ForeignAddress);
            info.AppendFormat("\"{0}\",", this.ForeignPort);
            info.AppendFormat("\"{0}\",", this.State);

            return info.ToString();
        }

        public override string ToString() {
            StringBuilder output = new StringBuilder();

            output.AppendFormat("Protocol: {0}; ", this.Protocol);
            output.AppendFormat("LocalAddress: {0}; ", this.LocalAddress);
            output.AppendFormat("LocalPort: {0}; ", this.LocalPort);
            output.AppendFormat("ForeignAddress: {0}; ", this.ForeignAddress);
            output.AppendFormat("ForeignPort: {0}; ", this.ForeignPort);
            output.AppendFormat("State: {0}; ", this.State);
            output.AppendFormat("ProcessId: {0}; ", this.ProcessId);

            return output.ToString();
        }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
            if ((this.DetachedState != DCHealthCheck.DomainModel.DetachedState.Added) && (this.Id < 1)) {
                yield return new ValidationResult(
                    "Id is required.", new[] { "Id" });
            }

            if (this.DetachedState != DCHealthCheck.DomainModel.DetachedState.Deleted) {
                // add entity to SystemInfo, let DetectChanges perform relationship fixup
                if (this.SystemInfoId == Guid.Empty) {
                    yield return new ValidationResult(
                        "SystemInfoId is required.", new[] { "SystemInfoId" });
                }

                if ((this.SystemInfo != null) && (this.SystemInfo.Id != this.SystemInfoId)) {
                    yield return new ValidationResult(
                        "SystemInfo.Id does not match SystemInfoId.", new[] { "SystemInfo, SystemInfoId" });
                }

                if (this.ProcessInfo != null) {
                    if (this.ProcessInfo.Id != this.ProcessInfoRecordId) {
                        yield return new ValidationResult(
                            "ProcessInfo.Id does not match ProcessInfoRecordId.", new[] { "ProcessInfo, ProcessInfoRecordId" });
                    }
                }

                if (this.LocalAddress == null) {
                    yield return new ValidationResult(
                        "LocalAddress is required.", new[] { "LocalAddress" });
                }
                else if (this.LocalAddress.Length > LocalAddressMaxLength) {
                    yield return new ValidationResult(string.Format("LocalAddress length: {0} exceeds max length: {1}",
                        this.LocalAddress.Length, LocalAddressMaxLength), new[] { "LocalAddress" });
                }

                if (this.ForeignAddress == null) {
                    yield return new ValidationResult(
                        "ForeignAddress is required.", new[] { "ForeignAddress" });
                }
                else if (this.ForeignAddress.Length > ForeignAddressMaxLength) {
                    yield return new ValidationResult(string.Format("ForeignAddress length: {0} exceeds max length: {1}",
                        this.ForeignAddress.Length, ForeignAddressMaxLength), new[] { "ForeignAddress" });
                }

                if (this.State == NetworkConnectionState.Undefined) {
                    yield return new ValidationResult(
                        "State is required.", new[] { "State" });
                }
            } // if (this.DetachedState != DCHealthCheck.DomainModel.State.Deleted)
        }  
        #endregion
        
    }
}
